#include "File.h"
//#include <iostream>
#include <cassert>

#include <unistd.h>
#include <fcntl.h>

#include "rocksdb/slice.h"


namespace streamdb {


const int kMaxVarint64Length = 10;
inline char* EncodeVarint64(char* dst, uint64_t v) {
    static const unsigned int B = 128;
    unsigned char* ptr = reinterpret_cast<unsigned char*>(dst);
    while (v >= B) {
        *(ptr++) = (v & (B - 1)) | B;
        v >>= 7;
    }
    *(ptr++) = static_cast<unsigned char>(v);
    return reinterpret_cast<char*>(ptr);
}

const char* GetVarint64Ptr(const char* p, const char* limit, uint64_t* value) {
  uint64_t result = 0;
  for (uint32_t shift = 0; shift <= 63 && p < limit; shift += 7) {
    uint64_t byte = *(reinterpret_cast<const unsigned char*>(p));
    p++;
    if (byte & 128) {
      // More bytes are present
      result |= ((byte & 127) << shift);
    } else {
      result |= (byte << shift);
      *value = result;
      return reinterpret_cast<const char*>(p);
    }
  }
  return nullptr;
}


inline bool GetVarint64(rocksdb::Slice* input, uint64_t* value) {
  const char* p = input->data();
  const char* limit = p + input->size();
  const char* q = GetVarint64Ptr(p, limit, value);
  if (q == nullptr) {
    return false;
  } else {
    *input = rocksdb::Slice(q, static_cast<size_t>(limit - q));
    return true;
  }
}

inline int VarintLength(uint64_t v) {
  int len = 1;
  while (v >= 128) {
    v >>= 7;
    len++;
  }
  return len;
}



File::File(const std::string &fname) 
: fname(fname)
, fd(-1) 
{
}
File::~File(){
}

Status File::openRW(){
    fd = ::open(fname.c_str(), O_CREAT|O_RDWR, S_IRUSR|S_IWUSR|S_IRGRP);
    assert(fd != -1);
    return Status::OK();
}
Status File::close(){
    if(fd != -1){
        ::close(fd); 
        fd = -1;
    }
    return Status::OK();
}
Status File::getSize(uint64_t & size) {
    assert(fd != -1);
    size = ::lseek(fd, 0, SEEK_END);
    return Status::OK();
}
Status File::readUint64(uint64_t & value){
    assert(fd != -1);
    char buf[kMaxVarint64Length+1];
    int ret = ::read(fd, (void *)(buf), sizeof(char));
    if(ret == 0){
        return Status::EndOfFile();
    }
    else if(ret < 0){
        return Status::Failed("read file error!");
    }
    //std::cout << "buf[0]:" << static_cast<int>(buf[0]) << std::endl;
    ret = ::read(fd, (void*)(buf+1), (size_t)buf[0]);
    if(ret == 0){
        return Status::EndOfFile();
    }
    
    if(nullptr == GetVarint64Ptr(buf+1, buf+buf[0]+1, &value)){
        return Status::ErrorFormat("bin-fmt error: get value failed");
    }
    return Status::OK();
}
Status File::read(char * buf, uint64_t & sz){
    assert(fd != -1);
    int ret = ::read(fd, buf, sz);
    if(ret == 0){
        return Status::EndOfFile();
    }
    return (ret == sz) ? Status::OK() : Status::Failed("read failed");
}
Status File::writeUint64(const uint64_t value){
    assert(fd != -1);
    char buf[kMaxVarint64Length+1];
    buf[0] = (char)VarintLength(value);

    char* ptr = EncodeVarint64(buf+1, value);
    int ret = ::write(fd, &buf, static_cast<size_t>(ptr - buf));
    if(ret < 0){
        return Status::Failed("write uint64_t failed");
    }
    return Status::OK();
}
Status File::write(const char * buf, uint64_t & cap){
    assert(fd != -1);
    int ret = ::write(fd, buf, cap);
    if(ret < 0){
        return Status::Failed("write buf failed");
    }
    cap =  ret;
    return Status::OK();
}
Status File::writeByPosition(const uint64_t pos, const char * buf, uint64_t & cap){
    auto s = seekOffset(pos);
    if(s.isOk()){
        return write(buf, cap);
    }
    return s;
}

Status File::seekBegin(){
    assert(fd != -1);
    int ret = ::lseek(fd, 0, SEEK_SET);
    return (ret == 0) ? Status::OK() : Status::Failed("seek failed");
}
Status File::seekEnd(){
    assert(fd != -1);
    int ret = ::lseek(fd, 0, SEEK_END);
    return (ret == 0) ? Status::OK() : Status::Failed("seek failed");
}
Status File::seekOffset(uint64_t offset){
    assert(fd != -1);
    int ret = ::lseek(fd, offset, SEEK_SET);
    return (ret >= 0) ? Status::OK() : Status::Failed("seek failed");
}
Status File::getCurPos(uint64_t & curPos){
    assert(fd != -1);
    curPos  = ::lseek(fd, 0, SEEK_SET);
    return Status::OK(); 
}
Status File::flush() {
    assert(fd != -1);
    int ret = ::fsync(fd);
    return (ret == 0) ? Status::OK() : Status::Failed("fsync failed");
}

}//namespace streamdb
